using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace AntEater.ComponentModel.BuildElements
{
    /// <summary>
    /// Provides a build element collection.
    /// </summary>
    [Serializable()]
    public class BuildElementCollection:Collection<IBuildElement>,ICloneable
    {
        /// <summary>
        /// Occurs when the content of the collection of the instance changed
        /// </summary>
        public event EventHandler<CollectionChangeEventArgs> CollectionChanged;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:BuildElementCollection"/> class.
        /// </summary>
        public BuildElementCollection ()
            :this(null){
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:BuildElementCollection"/> class.
        /// </summary>
        /// <param name="container">The container.</param>
        public BuildElementCollection(IContainerBuildElement container) {
            this._container = container;
        }

        private IContainerBuildElement _container;

        /// <summary>
        /// Gets or sets the container.
        /// </summary>
        /// <value>The container.</value>
        public IContainerBuildElement Container {
            get { return _container; }
        }

        /// <summary>
        /// Adds the range.
        /// </summary>
        /// <param name="collection">The collection.</param>
        public void AddRange(IEnumerable<IBuildElement> collection) {
            if (collection == null) { throw new ArgumentNullException("collection"); }

            foreach (IBuildElement item in collection) {
                this.Append(item);
            }
        }

        /// <summary>
        /// Inserts the element into the collection after the specified reference Element.
        /// If the element already exists in the tree, it is removed first.
        /// If the reference element is not found, the element is appended.
        /// </summary>
        /// <param name="element">The element to insert.</param>
        /// <param name="referenceElement">The reference element.</param>
        /// <returns>The inserted element.</returns>
        public IBuildElement InsertAfter(IBuildElement element, IBuildElement referenceElement) {

            if (element == null) { throw new ArgumentNullException("element"); }
            if (referenceElement == null) { throw new ArgumentNullException("referenceElement"); }

            //If the element already exists in this collection, remove it first.
            //This beacuse the index has to be computed as if the element doesn't exist in the collection
            if (this.Contains(element)) {
                this.Remove(element);
            }

            int insertIndex = this.IndexOf(referenceElement);

            if (insertIndex != -1) {
                this.Insert(insertIndex + 1, element);
            }
            else {
                this.Append(element);
            }

            return element;
        }

        /// <summary>
        /// Inserts the element into the collection before the specified reference Element.
        /// If the element already exists in the tree, it is removed first.
        /// If the reference element is not found, the element is prepended.
        /// </summary>
        /// <param name="element">The element to insert.</param>
        /// <param name="referenceElement">The reference element.</param>
        /// <returns>The inserted element.</returns>
        public IBuildElement InsertBefore(IBuildElement element, IBuildElement referenceElement) {
            
            if (element == null) { throw new ArgumentNullException("element"); }
            if (referenceElement == null) { throw new ArgumentNullException("referenceElement"); }

            //If the element already exists in this collection, remove it first.
            //This beacuse the index has to be computed as if the element doesn't exist in the collection
            if (this.Contains(element)) {
                this.Remove(element);
            }

            int insertIndex = this.IndexOf(referenceElement);

            if (insertIndex != -1) {
                this.Insert(insertIndex, element);
            }
            else {
                this.Prepend(element);
            }

            return element;
        }

        /// <summary>
        /// Appends the element to the collection.
        /// If the element already exists in the tree, it is removed first.
        /// </summary>
        /// <param name="element">The element to insert.</param>
        /// <returns>The appended element.</returns>
        public IBuildElement Append(IBuildElement element) {

            if (element == null) { throw new ArgumentNullException("element"); }

            //If the element already exists in this collection, remove it first.
            //This beacuse the index has to be computed as if the element doesn't exist in the collection
            if (this.Contains(element)) {
                this.Remove(element);
            }

            this.Insert(this.Count, element);
            return element;
        }

        /// <summary>
        /// Prepends the element to the collection.
        /// If the element already exists in the tree, it is removed first.
        /// </summary>
        /// <param name="element">The element to prepend.</param>
        /// <returns>The prepended element.</returns>
        public IBuildElement Prepend(IBuildElement element) {

            if (element == null) { throw new ArgumentNullException("element"); }

            //If the element already exists in this collection, remove it first.
            //This beacuse the index has to be computed as if the element doesn't exist in the collection
            if (this.Contains(element)) {
                this.Remove(element);
            }

            this.Insert(0, element);
            return element;
        }

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>
        public object Clone() {
            BuildElementCollection newCollection = new BuildElementCollection();

            foreach (IBuildElement element in this) {
                newCollection.Add(element.Clone() as IBuildElement);
            }

            return newCollection;
        }

        /// <summary>
        /// Inserts an element into the <see cref="T:System.Collections.ObjectModel.Collection`1"></see> at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index at which item should be inserted.</param>
        /// <param name="item">The object to insert. The value can be null for reference types.</param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">index is less than zero.-or-index is greater than <see cref="P:System.Collections.ObjectModel.Collection`1.Count"></see>.</exception>
        protected override void InsertItem(int index, IBuildElement item) {
            
            //Insert the item
            base.InsertItem(index, item);

            IContainerBuildElement oldContainer = item.Parent as IContainerBuildElement;

            //If the element already has a parent, remove the element from its parent
            if (oldContainer != null) {
                oldContainer.ChildElements.Remove(item);
            }

            //Set the new parent
            item.Parent = this.Container;
            
            OnCollectionChanged(CollectionChangeAction.Add);
        }

        /// <summary>
        /// Removes the element at the specified index of the <see cref="T:System.Collections.ObjectModel.Collection`1"></see>.
        /// </summary>
        /// <param name="index">The zero-based index of the element to remove.</param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">index is less than zero.-or-index is equal to or greater than <see cref="P:System.Collections.ObjectModel.Collection`1.Count"></see>.</exception>
        protected override void RemoveItem(int index) {
            this[index].Parent = null;
            base.RemoveItem(index);
            OnCollectionChanged(CollectionChangeAction.Remove);
        }

        /// <summary>
        /// Replaces the element at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index of the element to replace.</param>
        /// <param name="item">The new value for the element at the specified index. The value can be null for reference types.</param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">index is less than zero.-or-index is greater than <see cref="P:System.Collections.ObjectModel.Collection`1.Count"></see>.</exception>
        protected override void SetItem(int index, IBuildElement item) {
            this[index].Parent = null;
            base.SetItem(index, item);
            item.Parent = Container;
            OnCollectionChanged(CollectionChangeAction.Refresh);
        }

        /// <summary>
        /// Removes all elements from the <see cref="T:System.Collections.ObjectModel.Collection`1"></see>.
        /// </summary>
        protected override void ClearItems() {

            foreach (IBuildElement item in this) {
                item.Parent = null;
            }
            
            base.ClearItems();
            OnCollectionChanged(CollectionChangeAction.Refresh);
        }

        /// <summary>
        /// Called when [collection changed].
        /// </summary>
        /// <param name="action">The action.</param>
        protected void OnCollectionChanged(CollectionChangeAction action) {
            if (CollectionChanged != null) {
                CollectionChanged(this,new CollectionChangeEventArgs(action,this));
            }
        }
    }
}
